package owg.util.data;

import java.util.Arrays;

public class BitField
{	
	private byte[] field;
	/**Create a new, all-zero bitfield with the given size in bytes.*/
	public BitField(int initialSizeInBytes)
	{
		field = new byte[initialSizeInBytes];
	}
	/**Wrap the given array into a bitfield object. Operations on the bitfield will affect the array as long as the bitfield length does not change.*/
	public BitField(byte[] array)
	{
		field = array;
	}
	/**Sets the bit at the given index to 1 if the value is true and 0 if the value is false.
	 * <br/><br/>
	 * If the index is greater than the length of the bitfield(in bits) and the value is true, 
	 * then the bitfield is resized(and the internal array is replaced). The new length will 
	 * be at least double the current length, and at least big enough for the new bit to be indexable.
	 * <br/><br/>
	 * If the value is false and the index is out of range, then the bitfield is unchanged.*/
	public void set(int index, boolean value)
	{
		int arrayIndex = index/8;
		if(arrayIndex >= field.length)
		{
			if(value)
				field = Arrays.copyOf(field, Math.max(field.length*2, arrayIndex+1));
			else
				return;
		}
		int shift = index%8;
		int mask = 1<<shift;
		if(value)
			field[arrayIndex] |= mask;
		else
			field[arrayIndex] &= ~mask;
	}
	/**Gets the bit at the given index as a boolean(false if the bit is zero, true if it is one).
	 * Bits outside of the range are treated as zero.*/
	public boolean get(int index)
	{
		int arrayIndex = index/8;
		if(arrayIndex >= field.length)
			return false;
		int shift = index%8;
		int mask = 1<<shift;
		return (field[arrayIndex]&mask) != 0;
	}
	/**Apply the other bitfield to this bitfield using the bitwise OR operation.
	 * The length becomes equal to the longest of the two bitfields.
	 * @return This BitField*/
	public BitField or(BitField other)
	{
		if(field.length < other.field.length)
			field = Arrays.copyOf(field, other.field.length);
		for(int i = 0; i < other.field.length; i++)
		{
			field[i] |= other.field[i];
		}
		return this;
	}
	/**Sets the length of the bitfield in bytes. Bits outside of the new range are discarded.
	 * @return This BitField*/
	public BitField setLength(int lengthInBytes)
	{
		if(field.length != lengthInBytes)
			field = Arrays.copyOf(field, lengthInBytes);
		return this;
	}
	/**Invert all bits in the range.
	 * Note that if used without care, this can lead to unexpected behaviour because the length of the bitfield is variable.
	 * This means that even if two BitFields are equal by the definition of {@link #equals(Object)}, 
	 * they may become inequal after calling this method on both. Use in combination with {@link #setLength(int)} to prevent this behaviour.
	 * @return This BitField*/
	public BitField invert()
	{
		for(int i = 0; i < field.length; i++)
		{
			field[i] = (byte) ~(field[i]&0xFF);
		}
		return this;
	}
	/**Apply the other bitfield to this bitfield using the bitwise AND operation.
	 * The length is unchanged. If this bitfield is longer than the other, then bits outside of the other's range are set to zero.
	 * @return This BitField*/
	public BitField and(BitField other)
	{
		for(int i = 0; i < field.length; i++)
		{
			if(i < other.field.length)
				field[i] &= other.field[i];
			else
				field[i] = 0;
		}
		return this;
	}
	/**Returns true if all bits are zero.*/
	public boolean isZero()
	{
		for(int i = 0; i < field.length; i++)
		{
			if(field[i] != 0)
				return false;
		}
		return true;
	}
	/**Returns the length of the bitfield in bytes. The length in bytes is always an integer.*/
	public int lengthInBytes()
	{
		return field.length;
	}
	/**Returns the length of the bitfield in bits. The length in bits is always a multiple of 8.*/
	public int lengthInBits()
	{
		return field.length*8;
	}
	/**Returns a copy of the bitfield.*/
	public BitField clone()
	{
		return new BitField(field.clone());
	}
	
	@Override
	public boolean equals(Object obj)
	{
		if(obj instanceof BitField)
		{
			BitField other = (BitField)obj;
			for(int i = 0; i < Math.max(field.length, other.field.length); i++)
			{
				final byte a, b;
				if(i < field.length)
					a = field[i];
				else
					a = 0;
				if(i < other.field.length)
					b = other.field[i];
				else
					b = 0;
				if(a != b)
					return false;
			}
			return true;
		}
		else
			return false;
	}
	@Override
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < field.length; i++)
		{
			if(i != 0)
				sb.append(", ");
			String str = Integer.toBinaryString(field[i]&0xFF);
			sb.append(new String(new byte[8-str.length()]).replace("\0", "0"));
			sb.append(str);
		}
		return sb.toString();
	}
	
	public static void main(String[] args)
	{
		selfTest();
	}
	public static void selfTest()
	{
		BitField a = null, b = null;
		
		try 
		{
			a = new BitField(new byte[]{-1,-1,-1});
			b = new BitField(new byte[]{-1,-1,-1});
			a.invert();
			assert a.isZero();
			assert !a.equals(b);
			a.invert();
			assert a.equals(b);
			
			a = new BitField(4);
			assert a.isZero();
			a.set(0, true);
			b = new BitField(new byte[]{1});
			assert a.equals(b);
			b = new BitField(new byte[]{1,0,0,0,0,0});
			assert a.equals(b);
			assert b.equals(a);
			
			a.set(0, false);
			assert a.isZero();
			assert !a.equals(b);
			assert !b.equals(a);
			
			assert a.lengthInBytes() == 4;
			a.set(8*5, false);
			assert a.lengthInBytes() == 4;
			assert !a.get(8*5);
			assert !a.get(8*5-1);
			assert !a.get(8*5+1);
			a.set(8*5, true);
			assert a.lengthInBytes() >= 5;
			assert a.get(8*5);
			assert !a.get(8*5-1);
			assert !a.get(8*5+1);
			
			
			a = new BitField(new byte[]{3,2,1});
			b = new BitField(new byte[]{3,2});
			assert !a.equals(b);
			
			a.set(16, false);
			assert a.equals(b);
			
			a = new BitField(new byte[]{3,3,3});
			b = new BitField(new byte[]{});
			b.or(a);
			assert a.equals(b);
			assert a.lengthInBits() == b.lengthInBits();
			
			a = new BitField(new byte[]{1,2,3});
			b = new BitField(new byte[]{2,4,1});
			a.or(b);
			b = new BitField(new byte[]{3,6,3});
			assert a.equals(b);
			

			a = new BitField(new byte[]{1,6,3});
			b = new BitField(new byte[]{2,4,1});
			a.and(b);
			b = new BitField(new byte[]{0,4,1});
			assert a.equals(b);
			
			a = new BitField(new byte[]{1});
			b = new BitField(new byte[]{2,2,2});
			a.and(b);
			assert a.isZero();
			assert a.lengthInBytes() == 1;
			System.out.println(BitField.class.getSimpleName()+" self test OK");
		}
		catch(AssertionError e)
		{
			System.out.println("a = "+a);
			System.out.println("b = "+b);
			throw e;
		}
	}
	public void setByte(int byteIndex, byte value)
	{
		field[byteIndex] = value;
	}
	public byte getByte(int byteIndex)
	{
		return field[byteIndex];
	}
	
}
